package www.dashu.com.service.base;

import www.dashu.com.domain.CProduct;
import www.dashu.com.service.RedisService;
import www.dashu.com.dao.base.BaseDao;
import www.dashu.com.domain.base.BaseVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.*;
import org.springframework.util.StringUtils;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Table;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

public class BaseService<T, ID, DAO extends BaseDao>
{

    @Autowired
    public DAO dao;
    @Autowired
    public RedisService redisService;
    @PersistenceContext
    private EntityManager entityManager;

    public Optional<T> findById(ID id)
    {
        return dao.findById(id);
    }

    public Page<T> findAll(QueryParams queryParams)
    {
        Map<String, Object> queryMaps = new HashMap<>();

        Pageable pageable = beforeQuery(queryParams, queryMaps);

        return dao.findAll(new BaseDao.MySpecification(queryMaps), pageable);
    }

    public Page<T> findAllIncludeColumn(List<String> columns, QueryParams queryParams){
        Map<String, Object> queryMaps = new HashMap<>();

        Pageable pageable = beforeQuery(queryParams, queryMaps);

        Class domainClass = getDomainClass();
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery criteriaQuery = criteriaBuilder.createQuery();
        Root root = criteriaQuery.from(domainClass);

        if(StringUtils.isEmpty(columns)){
            throw new RuntimeException("如果不需要指定字段，那就请使用别的方法，查询全部字段");
        }

        List<Selection> list = columns.stream().map(column->root.get(column)).collect(Collectors.toList());
        Selection[] selections = new Selection[list.size()];
        list.toArray(selections);
        criteriaQuery.multiselect(selections);

        Predicate[] andCate = null;
        if(!StringUtils.isEmpty(queryMaps) && queryMaps.size() > 0){
            List<Predicate> andList = new ArrayList<>();
            queryMaps.forEach((key, value) -> {
                if (!StringUtils.isEmpty(value)) {
                    Path<Object> name = root.get(key);
                    Predicate predicate = null;
                    if(key.contains("id") || key.contains("status")){
                        predicate = criteriaBuilder.equal(name.as(String.class), String.valueOf(value));
                    }else {
                        predicate = criteriaBuilder.like(name.as(String.class), "%" + value + "%");
                    }
                    andList.add(predicate);
                }
            });
            andCate = new Predicate[andList.size()];
            andList.toArray(andCate);
            criteriaQuery.where(andCate);
        }

        String _sort = queryParams.getSort();
        String flag = _sort.substring(0, 1);
        if ("+".equals(flag)) {
            criteriaQuery.orderBy(criteriaBuilder.asc(root.get(_sort.substring(1))));
        } else {
            criteriaQuery.orderBy(criteriaBuilder.desc(root.get(_sort.substring(1))));
        }

        TypedQuery queryResut = entityManager.createQuery(criteriaQuery);

        queryResut.setFirstResult((queryParams.getPage()-1) * queryParams.getLimit());
        queryResut.setMaxResults(queryParams.getLimit());
        List products = queryResut.getResultList();

        List data = new ArrayList<>();
        if(!StringUtils.isEmpty(products)){
            products.forEach(result -> {
                try {
                    Object domain = domainClass.getConstructor().newInstance();
                    Field[] domainFields = domainClass.getDeclaredFields();
                    for (Field field : domainFields){
                        field.setAccessible(true);
                        String fName = field.getName();
                        if(columns.contains(fName)){
                            int index = columns.indexOf(fName);
                            Object[] item = (Object[])result;
                            field.set(domain, item[index]);
                        }
                    }
                    data.add(domain);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }

        CriteriaQuery<Long> cq = criteriaBuilder.createQuery(Long.class);
        cq.select(criteriaBuilder.count(cq.from(domainClass)));
        if(null != andCate){
            cq.where(andCate);
        }
        Long totalCount = entityManager.createQuery(cq).getSingleResult();

        Page<T> pageData = new PageImpl(data, pageable, totalCount);

        return pageData;
    }

    public List<T> findAll(Map<String, Object> queryMaps)
    {
        return dao.findAll(new BaseDao.MySpecification(queryMaps));
    }

    public List<T> findAll()
    {
        return dao.findAll();
    }

    public void deleteById(Long id)
    {
        dao.deleteById(id);
    }

    public void save(T t)
    {
        dao.save(t);
    }

    public void saveAll(List<T> list)
    {
        dao.saveAll(list);
    }

    public long createId(BaseVo vo){
        Table tableAnnotation = vo.getClass().getAnnotation(Table.class);
        String realTableName = tableAnnotation.name();
        long id = redisService.generate("ids:" + realTableName);
        return id;
    }

    public Class getDomainClass(){
        try {
            Method getTargetSourceMethod = dao.getClass().getDeclaredMethod("getTargetSource");
            Object jpaRepositoryTargetObj = getTargetSourceMethod.invoke(dao);
            Field field = jpaRepositoryTargetObj.getClass().getDeclaredField("target");
            field.setAccessible(true);
            Object jpaRepositoryObj = field.get(jpaRepositoryTargetObj);
            Method getDomainClassMethod = jpaRepositoryObj.getClass().getDeclaredMethod("getDomainClass");
            getDomainClassMethod.setAccessible(true);
            Class clazz = (Class) getDomainClassMethod.invoke(jpaRepositoryObj);
            return clazz;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public Pageable beforeQuery(QueryParams queryParams, Map<String, Object> queryMaps){
        String _sort = queryParams.getSort();
        String flag = _sort.substring(0, 1);
        Sort sort;
        if ("+".equals(flag)) {
            sort = Sort.by(Sort.Direction.ASC, _sort.substring(1));
        } else {
            sort = Sort.by(Sort.Direction.DESC, _sort.substring(1));
        }
        Pageable pageable = PageRequest.of(queryParams.getPage()-1, queryParams.getLimit(), sort);

        Field[] fieldsCurrent = queryParams.getClass().getDeclaredFields();
        Field[] superFields = queryParams.getClass().getSuperclass().getDeclaredFields();
        List<Field> fieldList = new ArrayList<>();
        fieldList.addAll(Arrays.asList(fieldsCurrent));
        fieldList.addAll(Arrays.asList(superFields));
        Field[] fields = new Field[fieldList.size()];
        fieldList.toArray(fields);

        for (Field field : fields) {
            field.setAccessible(true);
            Object value = null;
            try {
                value = field.get(queryParams);
            } catch (Exception e) {
                e.printStackTrace();
            }

            if(!StringUtils.isEmpty(value) && !field.getName().toLowerCase().equals("page") && !field.getName().toLowerCase().equals("limit") && !field.getName().toLowerCase().equals("sort") ){
                queryMaps.put(field.getName(),value);
            }
        }
        return pageable;
    }

}
